﻿Imports System.Collections
Imports System.Linq
Imports System.Collections.Generic
Imports System.IO

Imports System.Runtime.Serialization.Formatters.Binary

Public Class CacheMannager
    Private Const CacheFileName As String = "cache.Grefresh"

    Private Shared Sub initializeCache()
        If usePhisicalCache Then
            Try
                Try

                    Dim testCache = Path.Combine(CacheDataPath, "temp.tmp")
                    Using sw As New StreamWriter(testCache, False)
                        sw.WriteLine(DateTime.Now.ToString("yyyyMMddHHmm"))
                        sw.Close()
                    End Using

                    Using sw As New StreamWriter(testCache, True)
                        sw.WriteLine(DateTime.Now.ToString("yyyyMMddHHmm"))
                        sw.Close()
                    End Using

                    File.Delete(testCache)
                Catch ex As Exception

                    Throw ex
                End Try



                Dim dPath As String
                dPath = Path.Combine(CacheDataPath, CacheFileName)
                Using sw As New StreamWriter(dPath, False)
                    sw.WriteLine(DateTime.Now.ToString("yyyyMMddHHmm"))
                    sw.Close()
                End Using

                If File.Exists(CACHE_FILE_HASH_PATH) Then
                    Dim _cache1 = CType(ReflectionHelper.Deserialize(CACHE_FILE_HASH_PATH), List(Of KeyValuePair(Of String, CacheContext)))
                    If Not _cache1 Is Nothing Then
                        For Each item In _cache1
                            _cache.Add(item.Key, item.Value)
                        Next
                    End If
                End If
            Catch ex As Exception

                Throw ex
            End Try
        End If
       
    End Sub

    Private Shared ReadOnly Property CacheFilesPath() As String
        Get
            Dim fileFolder = Path.Combine(CacheDataPath, "cache.files")

            If Not Directory.Exists(fileFolder) Then
                Directory.CreateDirectory(fileFolder)
            End If
            Return fileFolder
        End Get
    End Property


    Private Shared CACHE_FILE_HASH_PATH As String = Path.Combine(CacheFilesPath, "cache.Gcache")

    Private Shared Sub PhisicalStorageCache()
        If usePhisicalCache Then


            Try
                Dim lista = _cache.Where(Function(x) _
                             x.Value IsNot Nothing _
                             AndAlso x.Value.Value.GetType().IsSerializable = True _
                             AndAlso x.Value.Minutes = Double.MaxValue _
                             AndAlso (Not x.Value.Value.GetType().FullName.Contains("System.Collections.Generic") OrElse (x.Value.Value.GetType().FullName.Contains("System.Collections.Generic") AndAlso (x.Value.Value.GetType().GetGenericArguments().First().IsSerializable))) _
                             ).ToList()

                If Not lista.Any AndAlso lista.GetType().IsSerializable = False Then
                    Exit Sub
                End If

                ReflectionHelper.Serialize(CACHE_FILE_HASH_PATH, lista)
            Catch ex As Exception
                Throw ex
            End Try
        End If
    End Sub



    Private Shared _CacheDataPath As String = Nothing
    Public Shared ReadOnly Property CacheDataPath() As String
        Get
            If _CacheDataPath Is Nothing Then

                _CacheDataPath = Configuration.CfgManager.App_Data_Path
            End If
            Return _CacheDataPath
        End Get
    End Property

    Private Shared WithEvents FSW As New FileSystemWatcher
    Private Shared Sub FSW_Changed(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs)
        CleanAll()

        initializeCache()

    End Sub

    Private Shared ReadOnly Property usePhisicalCache As Boolean
        Get

            Return Configuration.CfgManager.getAppSetting(Of Boolean)("usePhisicalCache")
        End Get
    End Property

    Private Shared ReadOnly Property useInfiniteCache As Boolean
        Get 
            Return Configuration.CfgManager.getAppSetting(Of Boolean)("useInfiniteCache")
        End Get
    End Property


    Public Shared Sub CleanKey(Of TCLass)(ByVal key As String)
        key = getKeyName(Of TCLass)(key)
        _cache.Remove(key)
         
            PhisicalStorageCache()


    End Sub

    Public Shared Sub CleanAllEntriesContainsKey(ByVal key As String)
        Try
            Dim q = _cache.Where(Function(x) x.Key.Contains(key)).Select(Function(x) x.Key).ToArray()

            For Each item In q
                Try
                    _cache.Remove(item)
                Catch ex As Exception
                    'Exceptions.ExceptionSelector.Select(ex)
                End Try
            Next

        Catch ex As Exception
            Throw ex
        End Try

        PhisicalStorageCache()
    End Sub


    Public Shared Sub CleanAllKeys(Of TClass)()
        Dim n = GetType(TClass).FullName
        Dim l = _cache.Where(Function(x) x.Key.StartsWith(n)).Select(Function(c) c.Key)
        For Each k As String In l
            _cache.Remove(k)
        Next
        PhisicalStorageCache()
    End Sub

    Public Shared Sub CleanAll()

        _cache.Clear()

        For Each fFile In Directory.GetFiles(CacheFilesPath)
            Try
                File.Delete(fFile)
            Catch
            End Try
        Next
        PhisicalStorageCache()


    End Sub

    <Serializable()> _
    Private Class CacheContext
        Public Key As String
        Public Value As Object
        Public DateInit As Date
        Public Minutes As Double
        Public Sub New(ByVal key As String, ByVal value As Object, Optional ByVal ExpiresMin As Double = 15)
            Me.DateInit = DateTime.Now
            Me.Key = key
            Me.Value = value
            Me.Minutes = ExpiresMin
        End Sub
        Public ReadOnly Property isExpired() As Boolean
            Get
                Return (DateTime.Now - DateInit).TotalMinutes > Minutes
            End Get
        End Property
    End Class

    Private Shared _cache As Dictionary(Of String, CacheContext)

    Public Shared Function getKeyName(Of TClass)(ByVal key As String) As String
        Return (GetType(TClass).FullName & key).ToUpper()
    End Function

    Shared Sub New()
        Try

            _cache = New Dictionary(Of String, CacheContext)

            If usePhisicalCache Then
                initializeCache()
                With FSW
                    .Path = CacheDataPath
                    .Filter = CacheFileName
                    .IncludeSubdirectories = False
                    .NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
                    ' Add event handlers.
                    AddHandler .Changed, AddressOf FSW_Changed
                    AddHandler .Created, AddressOf FSW_Changed
                    AddHandler .Deleted, AddressOf FSW_Changed
                    AddHandler .Renamed, AddressOf FSW_Changed
                    ' Begin watching.
                    .EnableRaisingEvents = True
                End With
            End If
        Catch ex As Exception
            Throw ex 
        End Try 
    End Sub

    Public Shared Function ExistsInCache(Of TClass)(ByVal key As String) As Boolean
        Try
            SyncLock _cache
                Dim l = _cache.Where(Function(x) x.Value.isExpired = True).Select(Function(x) x.Key).ToArray()

                For Each item As String In l
                    Try
                        _cache.Remove(item)
                    Catch
                    End Try
                Next

                Return _cache.Where(Function(x) x.Key.Equals(getKeyName(Of TClass)(key), StringComparison.InvariantCultureIgnoreCase)).Any()
            End SyncLock
        Catch
            Return Nothing
        End Try

    End Function

    Public Shared Sub SetInCache(Of TClass, T)(ByVal key As String, ByVal value As T, Optional ByVal ExpiresMin As Double = 15)

        Try
            SyncLock New Object
                If (useInfiniteCache) Then
                    ExpiresMin = -1
                End If

                If ExistsInCache(Of TClass)(key) Then
                    _cache.Remove(key)
                End If

                If ExpiresMin = -1 Then
                    ExpiresMin = Double.MaxValue
                End If

                If value Is Nothing Then
                    If (ExpiresMin = Double.MaxValue) Then
                        PhisicalStorageCache() 'manda a cachear localmente
                    End If
                    Return
                End If
                key = getKeyName(Of TClass)(key)

                Dim tttt = GetType(T)
                If tttt Is GetType(DataSet) Then
                    Dim v As DataSet = CType(CObj(value), DataSet).Copy()
                    _addKey(key, New CacheContext(key, v, ExpiresMin))

                ElseIf tttt Is GetType(DataTable) Then
                    Dim v As DataTable = CType(CObj(value), DataTable).Copy()
                    _addKey(key, New CacheContext(key, v, ExpiresMin))
                Else
                    _addKey(key, New CacheContext(key, value, ExpiresMin))
                End If

                If (ExpiresMin = Double.MaxValue) Then
                    PhisicalStorageCache() 'manda a cachear localmente
                End If
            End SyncLock
        
        Catch ex As Exception
            'Logging.Log()
        End Try

    End Sub

    Private Shared Sub _addKey(ByVal key As String, ByVal value As CacheContext)
        Try
            SyncLock _cache
                If _cache.ContainsKey(key) Then
                    _cache.Remove(key)
                End If
                _cache.Add(key, value)
            End SyncLock
        Catch
            Try : _cache(key) = value : Catch : End Try
        End Try
    End Sub

    Public Shared Function getFromCache(Of TClass, T)(ByVal Key As String) As T

        Try
            SyncLock _cache
                If ExistsInCache(Of TClass)(Key) Then
                    Dim v As Object
                    v = _cache.Where(Function(x) x.Key.Equals(getKeyName(Of TClass)(Key))).FirstOrDefault().Value.Value
                    Return CType(v, T)
                Else
                    Return Nothing
                End If
            End SyncLock
            
        Catch ex As Exception
            'Exceptions.ExceptionSelector.Select(ex)
            Return Nothing
        End Try
    End Function


End Class
